<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 4.2.1">
  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16.png">
  <link rel="mask-icon" href="/images/logo.svg" color="#222">

<link rel="stylesheet" href="/css/main.css">


<link rel="stylesheet" href="/lib/font-awesome/css/all.min.css">

<script id="hexo-configurations">
    var NexT = window.NexT || {};
    var CONFIG = {"hostname":"www.lixiaofei.cn","root":"/","scheme":"Mist","version":"7.8.0","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12,"onmobile":false},"copycode":{"enable":false,"show_result":false,"style":null},"back2top":{"enable":true,"sidebar":false,"scrollpercent":false},"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":false,"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":true,"storage":true,"lazyload":true,"nav":{"valine":{"order":-1}}},"algolia":{"hits":{"per_page":10},"labels":{"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}},"localsearch":{"enable":false,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}}};
  </script>

  <meta name="description" content="带薪玩游戏，是多么开心的事情，我就找到了。 前段时间，公司接到一个模拟业务场景的项目，需要在图形界面上模拟业务场景，比如人跑动，拖拽物体等，从而获取不太业务场景的模拟数据。 想了一下，不就是编个游戏吗？之前写过 骰筛子，和 模拟疫情扩散 的程序，于是果断请缨……">
<meta property="og:type" content="article">
<meta property="og:title" content="只需一招，Python 将系统秒变在线版！">
<meta property="og:url" content="http://www.lixiaofei.cn/2021-07-23-pygame/index.html">
<meta property="og:site_name" content="管窥程序">
<meta property="og:description" content="带薪玩游戏，是多么开心的事情，我就找到了。 前段时间，公司接到一个模拟业务场景的项目，需要在图形界面上模拟业务场景，比如人跑动，拖拽物体等，从而获取不太业务场景的模拟数据。 想了一下，不就是编个游戏吗？之前写过 骰筛子，和 模拟疫情扩散 的程序，于是果断请缨……">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="http://www.justdopython.com/assets/images/2021/07/pygame/00.png">
<meta property="og:image" content="http://www.justdopython.com/assets/images/2021/07/pygame/01.gif">
<meta property="article:published_time" content="2021-07-22T16:00:00.000Z">
<meta property="article:modified_time" content="2021-08-19T04:19:07.814Z">
<meta property="article:author" content="太阳雪">
<meta property="article:tag" content="编程">
<meta property="article:tag" content="游戏">
<meta property="article:tag" content="Pygame">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="http://www.justdopython.com/assets/images/2021/07/pygame/00.png">

<link rel="canonical" href="http://www.lixiaofei.cn/2021-07-23-pygame/">


<script id="page-configurations">
  // https://hexo.io/docs/variables.html
  CONFIG.page = {
    sidebar: "",
    isHome : false,
    isPost : true,
    lang   : 'zh-CN'
  };
</script>

  <title>只需一招，Python 将系统秒变在线版！ | 管窥程序</title>
  






  <noscript>
  <style>
  .use-motion .brand,
  .use-motion .menu-item,
  .sidebar-inner,
  .use-motion .post-block,
  .use-motion .pagination,
  .use-motion .comments,
  .use-motion .post-header,
  .use-motion .post-body,
  .use-motion .collection-header { opacity: initial; }

  .use-motion .site-title,
  .use-motion .site-subtitle {
    opacity: initial;
    top: initial;
  }

  .use-motion .logo-line-before i { left: initial; }
  .use-motion .logo-line-after i { right: initial; }
  </style>
</noscript>

</head>

<body itemscope itemtype="http://schema.org/WebPage">
  <div class="container use-motion">
    <div class="headband"></div>

    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="切换导航栏">
      <span class="toggle-line toggle-line-first"></span>
      <span class="toggle-line toggle-line-middle"></span>
      <span class="toggle-line toggle-line-last"></span>
    </div>
  </div>

  <div class="site-meta">

    <a href="/" class="brand" rel="start">
      <span class="logo-line-before"><i></i></span>
      <h1 class="site-title">管窥程序</h1>
      <span class="logo-line-after"><i></i></span>
    </a>
      <p class="site-subtitle" itemprop="description">Today is a gift</p>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger">
    </div>
  </div>
</div>




<nav class="site-nav">
  <ul id="menu" class="main-menu menu">
        <li class="menu-item menu-item-home">

    <a href="/" rel="section"><i class="fa fa-home fa-fw"></i>首页</a>

  </li>
        <li class="menu-item menu-item-about">

    <a href="/about/" rel="section"><i class="fa fa-user fa-fw"></i>关于</a>

  </li>
        <li class="menu-item menu-item-tags">

    <a href="/tags/" rel="section"><i class="fa fa-tags fa-fw"></i>标签</a>

  </li>
        <li class="menu-item menu-item-categories">

    <a href="/categories/" rel="section"><i class="fa fa-th fa-fw"></i>分类</a>

  </li>
        <li class="menu-item menu-item-archives">

    <a href="/archives/" rel="section"><i class="fa fa-archive fa-fw"></i>归档</a>

  </li>
  </ul>
</nav>




</div>
    </header>

    
  <div class="back-to-top">
    <i class="fa fa-arrow-up"></i>
    <span>0%</span>
  </div>
  <div class="reading-progress-bar"></div>

  <a href="https://github.com/alisx" class="github-corner" title="Follow me on GitHub" aria-label="Follow me on GitHub" rel="noopener" target="_blank"><svg width="80" height="80" viewBox="0 0 250 250" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a>


    <main class="main">
      <div class="main-inner">
        <div class="content-wrap">
          

          <div class="content post posts-expand">
            

    
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://www.lixiaofei.cn/2021-07-23-pygame/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.png">
      <meta itemprop="name" content="太阳雪">
      <meta itemprop="description" content="总结 技术 思考 想法">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="管窥程序">
    </span>
      <header class="post-header">
        <h1 class="post-title" itemprop="name headline">
          只需一招，Python 将系统秒变在线版！
        </h1>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>

              <time title="创建时间：2021-07-23 00:00:00" itemprop="dateCreated datePublished" datetime="2021-07-23T00:00:00+08:00">2021-07-23</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2021-08-19 12:19:07" itemprop="dateModified" datetime="2021-08-19T12:19:07+08:00">2021-08-19</time>
              </span>

          
            <span id="/2021-07-23-pygame/" class="post-meta-item leancloud_visitors" data-flag-title="只需一招，Python 将系统秒变在线版！" title="阅读次数">
              <span class="post-meta-item-icon">
                <i class="fa fa-eye"></i>
              </span>
              <span class="post-meta-item-text">阅读次数：</span>
              <span class="leancloud-visitors-count"></span>
            </span>
  
  <span class="post-meta-item">
    
      <span class="post-meta-item-icon">
        <i class="far fa-comment"></i>
      </span>
      <span class="post-meta-item-text">Valine：</span>
    
    <a title="valine" href="/2021-07-23-pygame/#valine-comments" itemprop="discussionUrl">
      <span class="post-comments-count valine-comment-count" data-xid="/2021-07-23-pygame/" itemprop="commentCount"></span>
    </a>
  </span>
  
  

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
        <p><img src="http://www.justdopython.com/assets/images/2021/07/pygame/00.png" alt="标题图"><br>
带薪玩游戏，是多么开心的事情，我就找到了。</p>
<p>前段时间，公司接到一个模拟业务场景的项目，需要在图形界面上模拟业务场景，比如人跑动，拖拽物体等，从而获取不太业务场景的模拟数据。</p>
<p>想了一下，不就是编个游戏吗？之前写过 <a href="https://mp.weixin.qq.com/s/czcGKk6RTrZVi6-KRUAR0w" target="_blank" rel="noopener">骰筛子</a>，和 <a href="https://mp.weixin.qq.com/s/BJ0GdZ5ipGNCIAaDh0C01A" target="_blank" rel="noopener">模拟疫情扩散</a> 的程序，于是果断请缨……</p>
<a id="more"></a>
<p>经过一个星期的尝试，终于用 Pygame 实现了业务场景的可视化模拟，坐等收钱。</p>
<p>由于商务限制，无法展示模拟程序，所以今天制作一个 <a href="https://www.pygame.org/docs/tut/ChimpLineByLine.html" target="_blank" rel="noopener" title="打猴子">打猴子</a> 游戏，来介绍 Pygame 的一些用法，这个游戏也是我完成模拟程序的主要学习对象。</p>
<h2 id="重识-Pygame">重识 Pygame</h2>
<p>之前对 Pygame 的认识很肤浅，停留在画图工具的层面上。</p>
<p>通过学习和探索之后，才发现 Pygame 的强度功能，而且有很多框架和组件，帮助开发者提高效率，比如对象类 <a href="https://www.pygame.org/docs/ref/sprite.html" target="_blank" rel="noopener" title="Sprite">Sprite</a> 等。</p>
<p>Pygame 中 Surface 是个很重要的概念，相当于 <a href="https://www.adobe.com/cn/products/photoshop.html" target="_blank" rel="noopener" title="Ps">Ps(Photo shop)</a> 的图层，图层之间可以叠加，可以相对所在图层定位，比如将背景图层相对屏幕定位后，背景图层上的其他图层可以相对于背景定位，这样就不用每次都计算一个图层想对于屏幕的具体位置了。</p>
<h2 id="打猴子游戏">打猴子游戏</h2>
<p><a href="https://github.com/pygame/pygame/blob/main/examples/chimp.py" target="_blank" rel="noopener" title="Chimp">Chimp</a> 是 Pygame 文档中的一个示例，帮助开发者学习和理解，我们就以这个游戏为，并且在原版基础上，做了些改动，比如加入了游戏统计信息的显示等。</p>
<p>最终效果是这样的:</p>
<p><img src="http://www.justdopython.com/assets/images/2021/07/pygame/01.gif" alt="打猴子游戏"></p>
<h3 id="思路">思路</h3>
<p>打猴子游戏，就是一只猴子在不断地跑，然后用鼠标控制一个拳头去击打它，玩的过程中会记录击中次数和击中率。</p>
<p>根据游戏思路，游戏中有猴子和拳头两个对象，对了提高可玩性，需要添加一些音效和动作。</p>
<h3 id="实现">实现</h3>
<p>实现过程将分为 游戏加载、对象设置、游戏主循环三个方面做说明。</p>
<h4 id="游戏资源">游戏资源</h4>
<p>游戏有两种资源，对象图像和声效。</p>
<p>图像资源可以是图片，格式可以是比如 bmp，jpg，png 等。</p>
<p>我们编写一个图像加载方法：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">load_image</span><span class="params">(name, colorkey=None)</span>:</span></span><br><span class="line">    fullname = os.path.join(<span class="string">'chimp_data'</span>, name)</span><br><span class="line">    <span class="keyword">try</span>: </span><br><span class="line">        image = pygame.image.load(fullname)</span><br><span class="line">    <span class="keyword">except</span> pygame.error <span class="keyword">as</span> message:</span><br><span class="line">        print(<span class="string">'Cannot load image:'</span>, name)</span><br><span class="line">        <span class="keyword">raise</span> SystemExit(message)</span><br><span class="line">    image = image.convert()</span><br><span class="line">    <span class="keyword">if</span> colorkey <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span>:</span><br><span class="line">        <span class="keyword">if</span> colorkey <span class="keyword">is</span> <span class="number">-1</span>:</span><br><span class="line">            colorkey = image.get_at((<span class="number">0</span>, <span class="number">0</span>))</span><br><span class="line">        image.set_colorkey(colorkey, RLEACCEL)</span><br><span class="line">    <span class="keyword">return</span> image, image.get_rect()</span><br></pre></td></tr></table></figure>
<ul>
<li><code>load_image</code> 方法用来加载一个图像，参数 <code>name</code> 为图像文件名，<code>colorkey</code> 为需要去除的关键颜色</li>
<li>利用 <code>Pygame.image.load</code> 用图片文件创建为 <code>image</code> 对象</li>
<li><code>image.convert()</code> 的作用是对图像做适应环境的优化，以便绘制更高效</li>
<li>如果 <code>colorkey</code> 参数没有提供的话，会将图像上第一个像素颜色作为 <code>colorkey</code></li>
<li><code>image.set_colorkey(colorkey, RLEACCEL)</code> 用于设置图像的 <code>colorkey</code>，其中 <code>RLEACCEL</code> 作用是对 <code>colorkey</code> 做除去操作</li>
<li>方法返回图像对象和图像的矩形区域，矩形区域在对象绘制时很重要</li>
</ul>
<p>声效资源的加载也类似：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">load_sound</span><span class="params">(name)</span>:</span></span><br><span class="line">    <span class="class"><span class="keyword">class</span> <span class="title">NoneSound</span>:</span></span><br><span class="line">        <span class="function"><span class="keyword">def</span> <span class="title">play</span><span class="params">(self)</span>:</span> <span class="keyword">pass</span></span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> pygame.mixer:</span><br><span class="line">        <span class="keyword">return</span> NoneSound()</span><br><span class="line">    fullname = os.path.join(<span class="string">'chimp_data'</span>, name)</span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        sound = pygame.mixer.Sound(fullname)</span><br><span class="line">    <span class="keyword">except</span> pygame.error <span class="keyword">as</span> message:</span><br><span class="line">        print(<span class="string">'Cannot load sound:'</span>, fullname)</span><br><span class="line">        <span class="keyword">raise</span> SystemExit(message)</span><br><span class="line">    <span class="keyword">return</span> sound</span><br></pre></td></tr></table></figure>
<ul>
<li><code>pygame.mixer</code> 是 Pygame 的声音合成器，如果电脑设备上没有声卡(声音处理组件)，将无法播放声音</li>
<li>定义了一个 <code>NoneSound</code> 类，实现了空的 play 方法，以便在没有声卡的设备上正常运行</li>
<li>最好返回声音资源</li>
</ul>
<p>通过 <code>load_image</code> 和 <code>load_sound</code> 两个方法，就完成了游戏中资源的加载。</p>
<h4 id="对象设置">对象设置</h4>
<p>现在可以定义游戏对象了，需要处理样式，形态，状态等，为了对象定义动作行为等。</p>
<p>如果光靠手工绘制，是很麻烦的，Pygame 提供了对象类 Sprite，只需要初始化数据，实现状态更新就可以了。</p>
<p>游戏需要两种对象：拳头和猴子。</p>
<p>拳头，需要在屏幕上显示一个拳头，并且需要跟着鼠标移动，当做打击动作时，需要变化形态，具体的代码如下：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Fist</span><span class="params">(pygame.sprite.Sprite)</span>:</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">__init__</span><span class="params">(self)</span>:</span></span><br><span class="line">        super().__init__(self)</span><br><span class="line">        self.image, self.rect = load_image(<span class="string">'fist.bmp'</span>, <span class="number">-1</span>)</span><br><span class="line">        self.punching = <span class="number">0</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">update</span><span class="params">(self)</span>:</span></span><br><span class="line">        <span class="string">"""拳头随鼠标移动"""</span></span><br><span class="line">        pos = pygame.mouse.get_pos()</span><br><span class="line">        self.rect.midtop = pos</span><br><span class="line">        <span class="keyword">if</span> self.punching:</span><br><span class="line">            self.rect.move_ip(<span class="number">5</span>, <span class="number">10</span>)</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">punch</span><span class="params">(self, target)</span>:</span></span><br><span class="line">        <span class="string">"""返回是否打击到目标"""</span></span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> self.punching:</span><br><span class="line">            self.punching = <span class="number">1</span></span><br><span class="line">            hitbox = self.rect.inflate(<span class="number">-5</span>, <span class="number">-5</span>)</span><br><span class="line">            <span class="keyword">return</span> hitbox.colliderect(target.rect)</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">unpunch</span><span class="params">(self)</span>:</span></span><br><span class="line">        <span class="string">"""结束打击"""</span></span><br><span class="line">        self.punching = <span class="number">0</span></span><br></pre></td></tr></table></figure>
<ul>
<li>在初始化方法 <code>__init__</code> 里，用 <code>load_image</code> 加载了拳头图像，获得了 <code>image</code> 和 <code>rect</code>，这两个属性是 <code>Sprite</code> 对象必须的</li>
<li><code>update</code> 是必须实现的 <code>Sprite</code> 方法，用来更新对象的状态：首先获得鼠标的位置坐标，并将位置赋予对象区域，即将对象区域的中上位置移动到鼠标位置，从而实现对象位置的变化</li>
<li>然后判读对象是否处于击打状态，如果是则将对象的相对位置，向右移动 5 个像素，向下移动 10 个像素，表示在向下挥舞拳头</li>
<li><code>punch</code> 是自定义方法，作为外边调用的接口。调用时会设置对象状态，并返回是否击中。具体是：如果击打的话，且当前不是在击打状态时，设置状态为击打中，并将对象的区域缩小 5 个像素，即打击下去的话拳头远去，区域会缩小。</li>
<li><code>colliderect</code> 方法用于做碰撞检测，即与目标的区域做判断，是否碰撞到一起</li>
<li><code>unpunch</code> 也是自定方法，结束击打之后，将击打状态设置为否</li>
</ul>
<p>仔细阅读代码就会发现，<code>Sprite</code> 让一个业务复杂的对象很简单地实现了，而且不用关注如何绘制。</p>
<p>同样的方式定义猴子对象，猴子对象需要自动移动，如果被打击到，会做旋转动作，具体代码如下：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Chimp</span><span class="params">(pygame.sprite.Sprite)</span>:</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">__init__</span><span class="params">(self)</span>:</span></span><br><span class="line">        super().__init__(self)</span><br><span class="line">        self.image, self.rect = load_image(<span class="string">'chimp.bmp'</span>, <span class="number">-1</span>)</span><br><span class="line">        screen = pygame.display.get_surface()</span><br><span class="line">        self.area = screen.get_rect()</span><br><span class="line">        self.rect.topleft = <span class="number">60</span>, <span class="number">10</span></span><br><span class="line">        self.move = <span class="number">9</span></span><br><span class="line">        self.dizzy = <span class="number">0</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">update</span><span class="params">(self)</span>:</span></span><br><span class="line">        <span class="string">"""根据状态调整猴子行为"""</span></span><br><span class="line">        <span class="keyword">if</span> self.dizzy:</span><br><span class="line">            self._spin()</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            self._walk()</span><br><span class="line">            <span class="keyword">pass</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">_walk</span><span class="params">(self)</span>:</span></span><br><span class="line">        <span class="string">"""猴子在屏幕之间移动，当碰到屏幕边缘时返回"""</span></span><br><span class="line">        newpos = self.rect.move((self.move, <span class="number">0</span>))</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> self.area.contains(newpos):</span><br><span class="line">            self.move = -self.move</span><br><span class="line">            newpos = self.rect.move((self.move, <span class="number">0</span>))</span><br><span class="line">            self.image = pygame.transform.flip(self.image, <span class="number">1</span>, <span class="number">0</span>)</span><br><span class="line">        self.rect = newpos</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">_spin</span><span class="params">(self)</span>:</span></span><br><span class="line">        <span class="string">"""旋转猴子图片"""</span></span><br><span class="line">        center = self.rect.center</span><br><span class="line">        self.dizzy += <span class="number">12</span></span><br><span class="line">        <span class="keyword">if</span> self.dizzy &gt;= <span class="number">360</span>:</span><br><span class="line">            self.dizzy = <span class="number">0</span></span><br><span class="line">            self.image = self.original</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            rotate = pygame.transform.rotate</span><br><span class="line">            self.image = rotate(self.original, self.dizzy)</span><br><span class="line">        self.rect = self.image.get_rect(center=center)</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">punched</span><span class="params">(self)</span>:</span></span><br><span class="line">        <span class="string">"""被击中时调用"""</span></span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> self.dizzy:</span><br><span class="line">            self.dizzy = <span class="number">1</span></span><br><span class="line">            self.original = self.image</span><br></pre></td></tr></table></figure>
<ul>
<li><code>__init__</code> 中，<code>area</code> 获取了屏幕的区域，以便判断猴子移动范围；move 为移动速度，即每帧移动多数像素，值越大速度越快</li>
<li><code>update</code> 方法中，根据对象状态调用对于状态的行为</li>
<li><code>_walk</code> 方法为走动行为，首先将对象区域移动到新位置，获得新位置范围 <code>newpos</code>，然后判断新位置是否在屏幕区域 <code>area</code> 中，没有的话，将移动距离设置为相反数，向相反的方向移动；<code>pygame.transform.flip</code> 的作用时让图片水平翻转，好像猴子掉了个头一样</li>
<li><code>_spin</code> 方法为转圈行为，当被打中之后，做360度旋转，<code>dizzy</code> 为旋转角度，每帧旋转 12 度，<code>rotate</code> 方法用于图像旋转，当旋转角度超出 360 度后，回复图像样式。</li>
<li><code>punched</code> 方法在被击中时调用，只做状态设置，其中 <code>original</code> 是将旋转前的图像暂存起来，用于旋转结束后的图像恢复</li>
</ul>
<blockquote>
<p><strong>请注意</strong> 图像旋转方法 <code>rotate</code>，虽然能产生旋转效果，但是却不能指定旋转中心点，其旋转中心点为将图像刚好放在一个正方形的左侧时，正方形的中心点作为旋转中心点。<br>
如果要实现围绕特定中心点旋转，就需要在旋转之后，将图像移动到这个点。<br>
这就是在 <code>_spin</code> 方法中，<code>self.rect = self.image.get_rect(center=center)</code> 这句代码的作用——让图像围绕图像中心点旋转。</p>
</blockquote>
<p>到这里，我们的游戏对象就设置完成了。</p>
<h4 id="理解主循环">理解主循环</h4>
<p>Pygame 中有个重要的概念就是主循环。</p>
<p>一般来说，会认为对象会自动发生移动和操作。</p>
<p>其实各种动作的游戏场景是由每帧图像合并而成的，就好比拍一个 pose，拍一张照，如此反复，最后将图片快速的切换，可以看到动画效果一样。</p>
<p>理解了这个，就能理解为什么电脑游戏的性能常常用更多的帧数来衡量的，更多的帧，意味着需要每秒绘制更多的图像。</p>
<p>Pygame 就是这么做的，通过一个主循环，不断地绘制对象的行为，从而产生动画效果。</p>
<p>主循环实际上就是个死循环，通过 <code>pygame.time.Clock</code> 控制游戏帧数。</p>
<p>来看看具体代码：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">main</span><span class="params">()</span>:</span></span><br><span class="line">    <span class="comment"># 初始化</span></span><br><span class="line">    pygame.init() </span><br><span class="line">    screen = pygame.display.set_mode((<span class="number">468</span>, <span class="number">90</span>))  <span class="comment"># 设置窗口大小，获得窗口屏幕</span></span><br><span class="line">    pygame.display.set_caption(<span class="string">'Monkey Fever'</span>)  <span class="comment"># 窗口标题</span></span><br><span class="line">    pygame.mouse.set_visible(<span class="number">0</span>)  <span class="comment"># 隐藏鼠标</span></span><br><span class="line"></span><br><span class="line">    background = pygame.Surface(screen.get_size()) <span class="comment"># 设置背景</span></span><br><span class="line">    background = background.convert()</span><br><span class="line">    background.fill((<span class="number">250</span>, <span class="number">250</span>, <span class="number">250</span>))</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> pygame.font:</span><br><span class="line">        font = pygame.font.SysFont(<span class="string">'SimHei'</span>,<span class="number">24</span>)  <span class="comment"># 设置字体</span></span><br><span class="line"></span><br><span class="line">    whiff_sound = load_sound(<span class="string">'whiff.wav'</span>)  <span class="comment"># 加载声音资源</span></span><br><span class="line">    punch_sound = load_sound(<span class="string">'punch.wav'</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 创建对象</span></span><br><span class="line">    chimp = Chimp()</span><br><span class="line">    fist = Fist()</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 将对象添加到对象控制组</span></span><br><span class="line">    allsprites = pygame.sprite.Group((fist, chimp))</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 获取游戏时钟</span></span><br><span class="line">    clock = pygame.time.Clock()</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 游戏统计</span></span><br><span class="line">    punchcount = <span class="number">0</span></span><br><span class="line">    hitcount = <span class="number">0</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> <span class="number">1</span>:</span><br><span class="line">        clock.tick(<span class="number">60</span>)  <span class="comment"># 设置游戏帧数</span></span><br><span class="line"></span><br><span class="line">        <span class="comment"># 事件循环</span></span><br><span class="line">        <span class="keyword">for</span> event <span class="keyword">in</span> pygame.event.get():</span><br><span class="line">            <span class="keyword">if</span> event.type == QUIT:</span><br><span class="line">                <span class="keyword">return</span></span><br><span class="line">            <span class="keyword">elif</span> event.type == KEYDOWN <span class="keyword">and</span> event.key == K_ESCAPE:</span><br><span class="line">                <span class="keyword">return</span></span><br><span class="line">            <span class="keyword">elif</span> event.type == MOUSEBUTTONDOWN:</span><br><span class="line">                punchcount += <span class="number">1</span></span><br><span class="line">                <span class="keyword">if</span> fist.punch(chimp):</span><br><span class="line">                    punch_sound.play()  <span class="comment"># 击中</span></span><br><span class="line">                    chimp.punched()</span><br><span class="line">                    hitcount += <span class="number">1</span></span><br><span class="line">                <span class="keyword">else</span>:</span><br><span class="line">                    whiff_sound.play()  <span class="comment"># 错过</span></span><br><span class="line">                    <span class="keyword">pass</span></span><br><span class="line">            <span class="keyword">elif</span> event.type == MOUSEBUTTONUP:</span><br><span class="line">                fist.unpunch()</span><br><span class="line"></span><br><span class="line">        bg = background.copy()</span><br><span class="line">        <span class="comment"># 合成游戏统计信息        </span></span><br><span class="line">        <span class="keyword">if</span> punchcount &gt; <span class="number">0</span>:</span><br><span class="line">            msg = <span class="string">"打中次数: %d 击中率: %d%s"</span> % (hitcount, round((hitcount/punchcount)*<span class="number">100</span>), <span class="string">"%"</span>)</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            msg = <span class="string">"挥舞拳头吧！"</span></span><br><span class="line">        <span class="comment"># 绘制统计信息</span></span><br><span class="line">        text = font.render(msg, <span class="number">1</span>, (<span class="number">10</span>, <span class="number">10</span>, <span class="number">10</span>))</span><br><span class="line">        textpos = text.get_rect(centerx=background.get_width()/<span class="number">2</span>)</span><br><span class="line">        bg.blit(text, textpos)</span><br><span class="line"></span><br><span class="line">        allsprites.update() <span class="comment"># 更新对象状态</span></span><br><span class="line">        screen.blit(bg, (<span class="number">0</span>, <span class="number">0</span>)) <span class="comment"># 绘制背景</span></span><br><span class="line">        allsprites.draw(screen) <span class="comment"># 绘制对象</span></span><br><span class="line">        pygame.display.flip() <span class="comment"># 刷新屏幕</span></span><br></pre></td></tr></table></figure>
<ul>
<li><code>main</code> 方法为游戏的主方法，会在游戏启动时调用</li>
<li>在 <code>while</code> 之前的代码主要做的是初始化和定义，需要注意字体的设置，如果需要显示汉字的话，需要加载中文字体，可以参考<a href="https://blog.csdn.net/szadrop/article/details/53462317" target="_blank" rel="noopener" title="Pygame 设置中文字体">这篇文章</a></li>
<li><code>while</code> 里面是个游戏的主循环，首先设置游戏帧数为 60 帧；然后进入事件循环，游戏过程中的事件都会存储在 Pygame 的 <code>event</code> 中，通过 <code>get</code> 方获取，对我们关心的事件进行处理，比如游戏退出事件，鼠标点击事件等</li>
<li>在鼠标点击中，判断是否打中猴子，如果打中，播放打中音效，并且调用猴子被击中的方法 <code>punched</code>；如果没有打中，播放挥舞空拳的音效。如果捕获到鼠标键抬起事件，则调用停止打击方法。</li>
<li>事件循环之后，进行画面绘制，首先是拷贝一个起始背景，因为在每次绘制都会污染背景。然后绘制游戏信息，将信息绘制在背景上。</li>
<li><code>allsprites.update()</code> 会调用对象组里的每个对象的 <code>update</code> 方法，完成对象的状态更新</li>
<li><code>allsprites.draw(screen)</code> 将对象图像绘制到窗口中</li>
<li><code>pygame.display.flip()</code> 刷新屏幕，我们就会看到新的游戏画面了</li>
</ul>
<p>至此游戏的主要编码工作就完成了，不到 200 行代码，做了一个可玩的小游戏，赶紧运行起来试试吧。</p>
<h2 id="总结">总结</h2>
<p>这个简单的游戏制作过程，让我更深入地理解了 Pygame 的特点和使用方法，建立起来游戏开发的基本认知。</p>
<p>总体来看，要学会一个新东西，需要正确的理解它的核心概念，与自己原来的知识体系相融合，就能很快习得，而能灵活应用的前提是多玩多练。</p>
<p>通过一个星期的探索实践，为客户解决了模拟业务场景的问题，坐等加薪。</p>
<p>期望通过这个练习也能帮助你扩展一项技能，开拓一条道路，比心。</p>
<blockquote>
<p>示例代码：<a href="https://github.com/JustDoPython/python-examples/tree/master/taiyangxue/pygame" target="_blank" rel="noopener">https://github.com/JustDoPython/python-examples/tree/master/taiyangxue/pygame</a></p>
</blockquote>

    </div>

    
    
    

      <footer class="post-footer">
          <div class="post-tags">
              <a href="/tags/%E7%BC%96%E7%A8%8B/" rel="tag"># 编程</a>
              <a href="/tags/%E6%B8%B8%E6%88%8F/" rel="tag"># 游戏</a>
              <a href="/tags/Pygame/" rel="tag"># Pygame</a>
          </div>

        

			<div id="wechat_subscriber" style="display: block; padding: 10px 0; margin: 20px auto; width: 100%; text-align: center">
    <img id="wechat_subscriber_qcode" src="/images/wechat-qcode.png" alt="太阳雪 wechat" style="width: 200px; max-width: 100%;"/>
    <div>关注我，获得更多精彩内容</div>
</div>

        
		
    <div class="post-nav">
      <div class="post-nav-item">
    <a href="/2021-07-10-readthedocs/" rel="prev" title="不会写文档，叫什么高级程序员">
      <i class="fa fa-chevron-left"></i> 不会写文档，叫什么高级程序员
    </a></div>
      <div class="post-nav-item">
    <a href="/2021-07-26-zero2one/" rel="next" title="从 0 到 1，自然而然">
      从 0 到 1，自然而然 <i class="fa fa-chevron-right"></i>
    </a></div>
    </div>
      </footer>
    
  </article>
  
  
  



          </div>
          
    <div class="comments" id="valine-comments"></div>

<script>
  window.addEventListener('tabs:register', () => {
    let { activeClass } = CONFIG.comments;
    if (CONFIG.comments.storage) {
      activeClass = localStorage.getItem('comments_active') || activeClass;
    }
    if (activeClass) {
      let activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
      if (activeTab) {
        activeTab.click();
      }
    }
  });
  if (CONFIG.comments.storage) {
    window.addEventListener('tabs:click', event => {
      if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
      let commentClass = event.target.classList[1];
      localStorage.setItem('comments_active', commentClass);
    });
  }
</script>

        </div>
          
  
  <div class="toggle sidebar-toggle">
    <span class="toggle-line toggle-line-first"></span>
    <span class="toggle-line toggle-line-middle"></span>
    <span class="toggle-line toggle-line-last"></span>
  </div>

  <aside class="sidebar">
    <div class="sidebar-inner">

      <ul class="sidebar-nav motion-element">
        <li class="sidebar-nav-toc">
          文章目录
        </li>
        <li class="sidebar-nav-overview">
          站点概览
        </li>
      </ul>

      <!--noindex-->
      <div class="post-toc-wrap sidebar-panel">
          <div class="post-toc motion-element"><ol class="nav"><li class="nav-item nav-level-2"><a class="nav-link" href="#重识-Pygame"><span class="nav-number">1.</span> <span class="nav-text">重识 Pygame</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#打猴子游戏"><span class="nav-number">2.</span> <span class="nav-text">打猴子游戏</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#思路"><span class="nav-number">2.1.</span> <span class="nav-text">思路</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#实现"><span class="nav-number">2.2.</span> <span class="nav-text">实现</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#游戏资源"><span class="nav-number">2.2.1.</span> <span class="nav-text">游戏资源</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#对象设置"><span class="nav-number">2.2.2.</span> <span class="nav-text">对象设置</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#理解主循环"><span class="nav-number">2.2.3.</span> <span class="nav-text">理解主循环</span></a></li></ol></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#总结"><span class="nav-number">3.</span> <span class="nav-text">总结</span></a></li></ol></div>
      </div>
      <!--/noindex-->

      <div class="site-overview-wrap sidebar-panel">
        <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
    <img class="site-author-image" itemprop="image" alt="太阳雪"
      src="/images/avatar.png">
  <p class="site-author-name" itemprop="name">太阳雪</p>
  <div class="site-description" itemprop="description">总结 技术 思考 想法</div>
</div>
<div class="site-state-wrap motion-element">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
          <a href="/archives/">
        
          <span class="site-state-item-count">88</span>
          <span class="site-state-item-name">日志</span>
        </a>
      </div>
      <div class="site-state-item site-state-categories">
            <a href="/categories/">
          
        <span class="site-state-item-count">14</span>
        <span class="site-state-item-name">分类</span></a>
      </div>
      <div class="site-state-item site-state-tags">
            <a href="/tags/">
          
        <span class="site-state-item-count">47</span>
        <span class="site-state-item-name">标签</span></a>
      </div>
  </nav>
</div>



      </div>

    </div>
  </aside>
  <div id="sidebar-dimmer"></div>


      </div>
    </main>

    <footer class="footer">
      <div class="footer-inner">
        

        

<div class="copyright">
  
  &copy; 
  <span itemprop="copyrightYear">2022</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">太阳雪</span>
</div>
  <div class="powered-by">由 <a href="https://hexo.io/" class="theme-link" rel="noopener" target="_blank">Hexo</a> & <a href="https://mist.theme-next.org/" class="theme-link" rel="noopener" target="_blank">NexT.Mist</a> 强力驱动
  </div>

        








      </div>
    </footer>
  </div>

  
  <script src="/lib/anime.min.js"></script>
  <script src="/lib/velocity/velocity.min.js"></script>
  <script src="/lib/velocity/velocity.ui.min.js"></script>

<script src="/js/utils.js"></script>

<script src="/js/motion.js"></script>


<script src="/js/schemes/muse.js"></script>


<script src="/js/next-boot.js"></script>




  
  <script>
    (function(){
      var canonicalURL, curProtocol;
      //Get the <link> tag
      var x=document.getElementsByTagName("link");
		//Find the last canonical URL
		if(x.length > 0){
			for (i=0;i<x.length;i++){
				if(x[i].rel.toLowerCase() == 'canonical' && x[i].href){
					canonicalURL=x[i].href;
				}
			}
		}
    //Get protocol
	    if (!canonicalURL){
	    	curProtocol = window.location.protocol.split(':')[0];
	    }
	    else{
	    	curProtocol = canonicalURL.split(':')[0];
	    }
      //Get current URL if the canonical URL does not exist
	    if (!canonicalURL) canonicalURL = window.location.href;
	    //Assign script content. Replace current URL with the canonical URL
      !function(){var e=/([http|https]:\/\/[a-zA-Z0-9\_\.]+\.baidu\.com)/gi,r=canonicalURL,t=document.referrer;if(!e.test(r)){var n=(String(curProtocol).toLowerCase() === 'https')?"https://sp0.baidu.com/9_Q4simg2RQJ8t7jm9iCKT-xh_/s.gif":"//api.share.baidu.com/s.gif";t?(n+="?r="+encodeURIComponent(document.referrer),r&&(n+="&l="+r)):r&&(n+="?l="+r);var i=new Image;i.src=n}}(window);})();
  </script>















  

  


<script>
NexT.utils.loadComments(document.querySelector('#valine-comments'), () => {
  NexT.utils.getScript('//unpkg.com/valine/dist/Valine.min.js', () => {
    var GUEST = ['nick', 'mail', 'link'];
    var guest = 'nick,mail';
    guest = guest.split(',').filter(item => {
      return GUEST.includes(item);
    });
    new Valine({
      el         : '#valine-comments',
      verify     : false,
      notify     : false,
      appId      : 'teRj7yeJj0xpfGKLJ5uFdbIu-gzGzoHsz',
      appKey     : '3uie0p1nxouCCnkjiXTEBeLd',
      placeholder: "您的关注是我的动力",
      avatar     : 'mm',
      meta       : guest,
      pageSize   : '10' || 10,
      visitor    : true,
      lang       : 'zh-cn' || 'zh-cn',
      path       : location.pathname,
      recordIP   : false,
      serverURLs : ''
    });
  }, window.Valine);
});
</script>

</body>
</html>
